<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="XiYTS" id="XiYTS"><span data-lake-id="ud1ec1cdf" id="ud1ec1cdf">典型回答</span></h1>
  <p data-lake-id="u273aaa2f" id="u273aaa2f"><span data-lake-id="u31e48a3a" id="u31e48a3a">二阶段提交(Two-phaseCommit)是XA分布式事务中一个重要的方案，二阶段提交的算法思路可以概括为：参与者将操作成败通知协调者，再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。</span></p>
  <p data-lake-id="ue4276f0c" id="ue4276f0c"><span data-lake-id="ua5914582" id="ua5914582">​</span><br></p>
  <p data-lake-id="u12f61fd6" id="u12f61fd6"><span data-lake-id="u706614da" id="u706614da">所谓的两个阶段是指：第一阶段：准备阶段(投票阶段)和第二阶段：提交阶段（执行阶段）。但是2PC本身存在着同步阻塞问题、单点故障问题、数据不一致问题等，所以在二阶段的基础上，增加了一个预提交的阶段，组成了3阶段提交的方案。</span></p>
  <p data-lake-id="u6b657b84" id="u6b657b84"><span data-lake-id="u0ac3f47d" id="u0ac3f47d">​</span><br></p>
  <h1 data-lake-id="n0VGB" id="n0VGB"><span data-lake-id="u19d7bcd0" id="u19d7bcd0">扩展知识</span></h1>
  <h2 data-lake-id="jxwzT" id="jxwzT" style="text-align: justify"><span data-lake-id="ua320f327" id="ua320f327">XA规范</span></h2>
  <p data-lake-id="ua37a8162" id="ua37a8162" style="text-align: justify"><span data-lake-id="u570ee265" id="u570ee265">X/Open 组织（即现在的 Open Group ）定义了分布式事务处理模型。 模型中主要包括应用程序（ AP ）、事务管理器（ TM ）、资源管理器（ RM ）、通信资源管理器（ CRM ）等四个角色。</span></p>
  <p data-lake-id="u033e352f" id="u033e352f" style="text-align: justify"><span data-lake-id="uc4fdecdc" id="uc4fdecdc">​</span><br></p>
  <p data-lake-id="u902a2c81" id="u902a2c81" style="text-align: justify"><span data-lake-id="u5f69b775" id="u5f69b775">一般，常见的事务管理器（ TM ）是交易中间件，常见的资源管理器（ RM ）是数据库，常见的通信资源管理器（ CRM ）是消息中间件。 </span></p>
  <p data-lake-id="ub0b457aa" id="ub0b457aa" style="text-align: justify"><span data-lake-id="u698d75d6" id="u698d75d6">​</span><br></p>
  <p data-lake-id="ue9a3e16d" id="ue9a3e16d" style="text-align: justify"><span data-lake-id="u8ae0a3cc" id="u8ae0a3cc">通常把一个数据库内部的事务处理，如对多个表的操作，作为本地事务看待。数据库的事务处理对象是本地事务，而分布式事务处理的对象是全局事务。</span></p>
  <p data-lake-id="uc211614f" id="uc211614f" style="text-align: justify"><span data-lake-id="u0faef4a8" id="u0faef4a8">​</span><br></p>
  <p data-lake-id="ub550f33f" id="ub550f33f"><span data-lake-id="u61906fd0" id="u61906fd0">所谓全局事务，是指分布式事务处理环境中，多个数据库可能需要共同完成一个工作，这个工作即是一个全局事务，例如，一个事务中可能更新几个不同的数据库。对数据库的操作发生在系统的各处但必须全部被提交或回滚。此时一个数据库对自己内部所做操作的提交不仅依赖本身操作是否成功，还要依赖与全局事务相关的其它数据库的操作是否成功，如果任一数据库的任一操作失败，则参与此事务的所有数据库所做的所有操作都必须回滚。 </span></p>
  <p data-lake-id="ud32dbecd" id="ud32dbecd"><span data-lake-id="u69216e1d" id="u69216e1d">​</span><br></p>
  <p data-lake-id="u77c5eef9" id="u77c5eef9"><span data-lake-id="ud8eca07d" id="ud8eca07d">XA 就是 X/Open DTP 定义的交易中间件与数据库之间的接口规范（即接口函数），交易中间件用它来通知数据库事务的开始、结束以及提交、回滚等。 XA 接口函数由数据库厂商提供。 </span></p>
  <p data-lake-id="uf6fac9ba" id="uf6fac9ba"><span data-lake-id="ue4675ba6" id="ue4675ba6">​</span><br></p>
  <p data-lake-id="u5e0b9b42" id="u5e0b9b42"><strong><span data-lake-id="u6269171f" id="u6269171f">二阶提交协议</span></strong><span data-lake-id="u4a9742a5" id="u4a9742a5">和</span><strong><span data-lake-id="u37cad459" id="u37cad459">三阶提交协议</span></strong><span data-lake-id="u8bc358ff" id="u8bc358ff">就是根据这一思想衍生出来的。可以说二阶段提交其实就是实现</span><strong><span data-lake-id="u199abac5" id="u199abac5">XA分布式事务的关键</span></strong><span data-lake-id="u237fd1a3" id="u237fd1a3">。</span></p>
  <p data-lake-id="ueece5f4a" id="ueece5f4a"><br></p>
  <h2 data-lake-id="nboXw" id="nboXw"><span data-lake-id="u64ab1089" id="u64ab1089">2PC</span></h2>
  <p data-lake-id="uba70a9bc" id="uba70a9bc" style="text-align: justify"><span data-lake-id="ua0d821d1" id="ua0d821d1">所谓的两个阶段是指：第一阶段：准备阶段(投票阶段)和第二阶段：提交阶段（执行阶段）。</span></p>
  <p data-lake-id="uc56a8f96" id="uc56a8f96" style="text-align: justify"><span data-lake-id="u4e5cf195" id="u4e5cf195"><br></span><span data-lake-id="u5c748367" id="u5c748367" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">在日常生活中其实是有很多事都是这种二阶段提交的，比如西方婚礼中就经常出现这种场景：</span></p>
  <p data-lake-id="uc939a7c7" id="uc939a7c7" style="text-align: justify"><span data-lake-id="u9ff722ec" id="u9ff722ec" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <blockquote data-lake-id="u08429a98" id="u08429a98">
   <p data-lake-id="u38f4e6fb" id="u38f4e6fb"><span data-lake-id="uc738712e" id="uc738712e">牧师：”你愿意娶这个女人吗?爱她、忠诚于她，无论她贫困、患病或者残疾，直至死亡。Doyou(你愿意吗)?”</span></p>
   <p data-lake-id="u3f9552c3" id="u3f9552c3"><span data-lake-id="u2661477d" id="u2661477d">新郎：”Ido(我愿意)!”</span></p>
   <p data-lake-id="ub2a0b62f" id="ub2a0b62f"><span data-lake-id="u43a78069" id="u43a78069">牧师：”你愿意嫁给这个男人吗?爱他、忠诚于他，无论他贫困、患病或者残疾，直至死亡。Doyou(你愿意吗)?”</span></p>
   <p data-lake-id="u231a7d53" id="u231a7d53"><span data-lake-id="u2ad354f0" id="u2ad354f0">新娘：”Ido(我愿意)!”</span></p>
   <p data-lake-id="u9abb8c03" id="u9abb8c03"><span data-lake-id="u4146d073" id="u4146d073">牧师：现在请你们面向对方，握住对方的双手，作为妻子和丈夫向对方宣告誓言。</span></p>
   <p data-lake-id="u1cb0308f" id="u1cb0308f"><span data-lake-id="u1171bee3" id="u1171bee3">新郎：我——某某某，全心全意娶你做我的妻子，无论是顺境或逆境，富裕或贫穷，健康或疾病，快乐或忧愁，我都将毫无保留地爱你，我将努力去理解你，完完全全信任你。我们将成为一个整体，互为彼此的一部分，我们将一起面对人生的一切，去分享我们的梦想，作为平等的忠实伴侣，度过今后的一生。</span></p>
   <p data-lake-id="u757a62f0" id="u757a62f0"><span data-lake-id="ub1f27dac" id="ub1f27dac">新娘：我全心全意嫁给你作为你的妻子，无论是顺境或逆境，富裕或贫穷，健康或疾病，快乐或忧愁，我都将毫无保留的爱你，我将努力去理解你，完完全全信任你，我们将成为一个整体，互为彼此的一部分，我们将一起面对人生的一切，去分享我们的梦想，作为平等的忠实伴侣，度过今后的一生。</span></p>
  </blockquote>
  <p data-lake-id="ubef5d14e" id="ubef5d14e" style="text-align: justify"><span data-lake-id="ua7a4e7ec" id="ua7a4e7ec" class="lake-fontsize-12" style="color: rgb(153, 153, 153)">​</span><br></p>
  <p data-lake-id="u6e4c8fe4" id="u6e4c8fe4" style="text-align: justify"><span data-lake-id="ucbee3328" id="ucbee3328" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">上面这个比较经典的桥段就是一个典型的二阶段提交过程。</span></p>
  <p data-lake-id="ue58b8154" id="ue58b8154" style="text-align: justify"><span data-lake-id="uec6dca76" id="uec6dca76" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <p data-lake-id="ua9c231e0" id="ua9c231e0" style="text-align: justify"><img src="https://cdn.nlark.com/yuque/0/2023/png/5378072/1676784906321-c8c1c42d-dd6a-44be-828f-e8db953a0ffe.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_14%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="u09d10359" id="u09d10359" style="text-align: justify"><span data-lake-id="u360ca3b0" id="u360ca3b0" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <p data-lake-id="u3b30e935" id="u3b30e935" style="text-align: justify"><span data-lake-id="u06614c0c" id="u06614c0c" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">首先协调者（牧师）会询问两个参与者（二位新人）是否能执行事务提交操作（愿意结婚）。如果两个参与者能够执行事务的提交，先执行事务操作，然后返回YES，如果没有成功执行事务操作，就返回NO。</span></p>
  <p data-lake-id="u93f1e306" id="u93f1e306" style="text-align: justify"><span data-lake-id="u11142aba" id="u11142aba" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <p data-lake-id="ued55f229" id="ued55f229" style="text-align: justify"><span data-lake-id="u5d9ed4cd" id="u5d9ed4cd" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">当协调者接收到所有的参与者的反馈之后，开始进入事务提交阶段。如果所有参与者都返回YES，那就发送COMMIT请求，如果有一个人返回NO，那就发送rollback请求。</span></p>
  <p data-lake-id="u358a057e" id="u358a057e" style="text-align: justify"><span data-lake-id="u57cdfabb" id="u57cdfabb" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <p data-lake-id="ud59b3a03" id="ud59b3a03" style="text-align: justify"><span data-lake-id="u16f09095" id="u16f09095">值得注意的是，二阶段提交协议的第一阶段准备阶段不仅仅是回答YES or NO，还是要执行事务操作的，只是执行完事务操作，并没有进行commit还是</span><span data-lake-id="ufa16acf2" id="ufa16acf2" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">rollback</span><span data-lake-id="uf62347aa" id="uf62347aa">。和上面的结婚例子不太一样。如果非要举例的话可以理解为男女双方交换定情信物的过程。信物一旦交给对方了，这个信物就不能挪作他用了。也就是说，一旦事务执行之后，在没有执行commit或者</span><span data-lake-id="udd1c2422" id="udd1c2422" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">rollback</span><span data-lake-id="u4b3ce352" id="u4b3ce352">之前，资源是被锁定的。这会造成阻塞。</span></p>
  <p data-lake-id="u3f0ca3a2" id="u3f0ca3a2"><span data-lake-id="u1028e529" id="u1028e529">​</span><br></p>
  <h3 data-lake-id="FWGPG" id="FWGPG"><span data-lake-id="ucb06254d" id="ucb06254d">2PC存在的问题</span></h3>
  <p data-lake-id="ueb0f89c6" id="ueb0f89c6"><span data-lake-id="u11f1fdc4" id="u11f1fdc4">二阶段提交中，最重要的问题是可能会带来数据不一致的问题，除此之外，还存在同步阻塞以及单点故障的问题。</span></p>
  <p data-lake-id="u60e6cadc" id="u60e6cadc"><span data-lake-id="ub76bfe69" id="ub76bfe69">​</span><br></p>
  <p data-lake-id="uca48429e" id="uca48429e"><span data-lake-id="ueabeca68" id="ueabeca68">首先看为什么会发生同步阻塞和单点故障的问题：</span></p>
  <p data-lake-id="uf676adf3" id="uf676adf3"><br></p>
  <p data-lake-id="u5d1025d6" id="u5d1025d6"><span data-lake-id="u9fddc9ce" id="u9fddc9ce">1、同步阻塞问题。执行过程中，所有参与节点都是事务阻塞型的。当参与者占有公共资源时，其他第三方节点访问公共资源不得不处于阻塞状态。</span></p>
  <p data-lake-id="u7802bdae" id="u7802bdae"><span data-lake-id="udf3c895b" id="udf3c895b">​</span><br></p>
  <p data-lake-id="ua8f66655" id="ua8f66655"><span data-lake-id="u1890fe74" id="u1890fe74">2、</span><span data-lake-id="uee15e5b1" id="uee15e5b1">单点故障</span><span data-lake-id="ua6adab75" id="ua6adab75">。由于协调者的重要性，一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段，协调者发生故障，那么所有的参与者还都处于锁定事务资源的状态中，而无法继续完成事务操作。（如果是协调者挂掉，可以重新选举一个协调者，但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题）</span></p>
  <p data-lake-id="u3507310c" id="u3507310c"><br></p>
  <p data-lake-id="u34652158" id="u34652158"><span data-lake-id="u6c5335bf" id="u6c5335bf">作为一个分布式的一致性协议，我们主要关注他可能带来的一致性问题的。2PC在执行过程中可能发生协调者或者参与者突然宕机的情况，在不同时期宕机可能有不同的现象。</span></p>
  <p data-lake-id="u0f5f6be7" id="u0f5f6be7"><span data-lake-id="u3fee44f0" id="u3fee44f0">​</span><br></p>
  <h3 data-lake-id="eDi9t" id="eDi9t"><span data-lake-id="u3eaee5ce" id="u3eaee5ce">情况一：协调者挂了，参与者没挂</span></h3>
  <p data-lake-id="u5089b008" id="u5089b008"><span data-lake-id="ub04bc596" id="ub04bc596">这种情况其实比较好解决，只要找一个协调者的替代者。当他成为新的协调者的时候，询问所有参与者的最后那条事务的执行情况，他就可以知道是应该做什么样的操作了。所以，这种情况不会导致数据不一致。</span></p>
  <p data-lake-id="u1ea847d4" id="u1ea847d4"><br></p>
  <h3 data-lake-id="HmODA" id="HmODA"><span data-lake-id="u66b04083" id="u66b04083">情况二：参与者挂了，协调者没挂</span></h3>
  <p data-lake-id="u18a30ed4" id="u18a30ed4"><span data-lake-id="u38ba0503" id="u38ba0503">这种情况其实也比较好解决。如果参与者挂了。那么之后的事情有两种情况：</span></p>
  <ul list="uf93c5728">
   <li fid="u45dcc1b4" data-lake-id="ua3f77e75" id="ua3f77e75"><span data-lake-id="u2639265b" id="u2639265b">第一个是挂了就挂了，没有再恢复。那就挂了呗，反正不会导致数据一致性问题。</span></li>
   <li fid="u45dcc1b4" data-lake-id="ufec87c9c" id="ufec87c9c"><span data-lake-id="u821bd7aa" id="u821bd7aa">第二个是挂了之后又恢复了，这时如果他有未执行完的事务操作，直接取消掉，然后询问协调者目前我应该怎么做，协调者就会比对自己的事务执行记录和该参与者的事务执行记录，告诉他应该怎么做来保持数据的一致性。</span></li>
  </ul>
  <h3 data-lake-id="BPhxN" id="BPhxN"><span data-lake-id="u1367711a" id="u1367711a">情况三：参与者挂了，协调者也挂了</span></h3>
  <p data-lake-id="u042aa763" id="u042aa763"><span data-lake-id="uba41f784" id="uba41f784">这种情况比较复杂，我们分情况讨论。</span></p>
  <ul list="u7702490e">
   <li fid="u12815147" data-lake-id="u8656a569" id="u8656a569"><span data-lake-id="u2b4069f0" id="u2b4069f0">协调者和参与者在第一阶段挂了。</span></li>
  </ul>
  <ul list="u7702490e" data-lake-indent="1">
   <li fid="udc0aed5e" data-lake-id="uf19f7b97" id="uf19f7b97"><span data-lake-id="u7a4d5009" id="u7a4d5009">由于这时还没有执行commit操作，新选出来的协调者可以询问各个参与者的情况，再决定是进行commit还是</span><span data-lake-id="u70f1b341" id="u70f1b341" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">rollback</span><span data-lake-id="u5d42e790" id="u5d42e790">。因为还没有commit，所以不会导致数据一致性问题。</span></li>
  </ul>
  <ul list="u7702490e" start="2">
   <li fid="u12815147" data-lake-id="u38bee276" id="u38bee276"><span data-lake-id="u10fcf13a" id="u10fcf13a">第二阶段协调者和参与者挂了，挂了的这个参与者在挂之前并没有接收到协调者的指令，或者接收到指令之后还没来的及做commit或者</span><span data-lake-id="u10133aad" id="u10133aad" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">rollback</span><span data-lake-id="u76bce21e" id="u76bce21e">操作。</span></li>
  </ul>
  <ul list="u7702490e" data-lake-indent="1">
   <li fid="u47ad6c19" data-lake-id="u6b203c96" id="u6b203c96"><span data-lake-id="u438cf697" id="u438cf697">这种情况下，当新的协调者被选出来之后，他同样是询问所有的参与者的情况。只要有机器执行了abort（</span><span data-lake-id="u46c4ddbb" id="u46c4ddbb" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">rollback</span><span data-lake-id="uebd1b7a5" id="uebd1b7a5">）操作或者第一阶段返回的信息是No的话，那就直接执行</span><span data-lake-id="u696ca9de" id="u696ca9de" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">rollback</span><span data-lake-id="u6d4f0580" id="u6d4f0580">操作。如果没有人执行abort操作，但是有机器执行了commit操作，那么就直接执行commit操作。这样，当挂掉的参与者恢复之后，只要按照协调者的指示进行事务的commit还是</span><span data-lake-id="u64c244c9" id="u64c244c9" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">rollback</span><span data-lake-id="ufedbf2f9" id="ufedbf2f9">操作就可以了。因为挂掉的机器并没有做commit或者</span><span data-lake-id="u79084ec0" id="u79084ec0" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">rollback</span><span data-lake-id="u588afe93" id="u588afe93">操作，而没有挂掉的机器们和新的协调者又执行了同样的操作，那么这种情况不会导致数据不一致现象。</span></li>
  </ul>
  <ul list="u7702490e" start="3">
   <li fid="u12815147" data-lake-id="uf24b7067" id="uf24b7067"><strong><span data-lake-id="u675ecc83" id="u675ecc83">第二阶段协调者和参与者挂了，挂了的这个参与者在挂之前已经执行了操作。但是由于他挂了，没有人知道他执行了什么操作。</span></strong></li>
  </ul>
  <ul list="u7702490e" data-lake-indent="1">
   <li fid="u737265af" data-lake-id="ue8525356" id="ue8525356"><span data-lake-id="u19d2e88b" id="u19d2e88b">这种情况下，新的协调者被选出来之后，如果他想负起协调者的责任的话他就只能按照之前那种情况来执行commit或者</span><span data-lake-id="uc63036be" id="uc63036be" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">rollback</span><span data-lake-id="u6a6f3a97" id="u6a6f3a97">操作。这样新的协调者和所有没挂掉的参与者就保持了数据的一致性，我们假定他们执行了commit。但是，这个时候，那个挂掉的参与者恢复了怎么办，因为他之前已经执行完了之前的事务，如果他执行的是commit那还好，和其他的机器保持一致了，万一他执行的是</span><span data-lake-id="u34821aa9" id="u34821aa9" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">rollback</span><span data-lake-id="u23112602" id="u23112602">操作那？这不就导致数据的不一致性了么？虽然这个时候可以再通过手段让他和协调者通信，再想办法把数据搞成一致的，但是，这段时间内他的数据状态已经是不一致的了！</span></li>
  </ul>
  <p data-lake-id="u5b33b873" id="u5b33b873"><br></p>
  <p data-lake-id="u466ae649" id="u466ae649"><span data-lake-id="u1fa4fba8" id="u1fa4fba8">所以，2PC协议中，如果出现协调者和参与者都挂了的情况，有可能导致数据不一致。</span></p>
  <p data-lake-id="u2000cbcb" id="u2000cbcb"><span data-lake-id="u4a446f0b" id="u4a446f0b">​</span><br></p>
  <p data-lake-id="u723dfce9" id="u723dfce9"><span data-lake-id="u58013371" id="u58013371">为了解决这个问题，衍生出了3PC。我们接下来看看3PC是如何解决这个问题的。</span></p>
  <p data-lake-id="u194ac75d" id="u194ac75d"><span data-lake-id="u99c2ba1e" id="u99c2ba1e">​</span><br></p>
  <h2 data-lake-id="vJSAr" id="vJSAr"><span data-lake-id="u353971bf" id="u353971bf">3PC</span></h2>
  <p data-lake-id="uaf4dafca" id="uaf4dafca"><br></p>
  <p data-lake-id="ua77bc0bd" id="ua77bc0bd" style="text-align: justify"><span data-lake-id="u8d93ae5b" id="u8d93ae5b">3PC最关键要解决的就是协调者和参与者同时挂掉的问题，所以3PC把2PC的准备阶段再次一分为二，这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。</span></p>
  <p data-lake-id="u075b017a" id="u075b017a" style="text-align: justify"><span data-lake-id="ue6f1ff30" id="ue6f1ff30">​</span><br></p>
  <p data-lake-id="ub3cfd128" id="ub3cfd128" style="text-align: justify"><img src="https://cdn.nlark.com/yuque/0/2023/png/5378072/1676784936348-cc28320f-c973-48eb-9521-19054badcba2.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_15%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="u7c04d0e2" id="u7c04d0e2" style="text-align: justify"><span data-lake-id="ud2bfd25e" id="ud2bfd25e">​</span><br></p>
  <p data-lake-id="u7f3410ed" id="u7f3410ed" style="text-align: justify"><span data-lake-id="u2e047724" id="u2e047724">在第一阶段，只是询问所有参与者是否可以执行事务操作，并不在本阶段执行事务操作。当协调者收到所有的参与者都返回YES时，在第二阶段才执行事务操作，然后在第三阶段在执行commit或者rollback。</span></p>
  <p data-lake-id="u00659988" id="u00659988" style="text-align: justify"><span data-lake-id="uf7c68fa2" id="uf7c68fa2">​</span><br></p>
  <p data-lake-id="u0667e290" id="u0667e290" style="text-align: justify"><span data-lake-id="ub2321649" id="ub2321649">这里再举一个生活中类似三阶段提交的例子：</span></p>
  <p data-lake-id="ue363bd5b" id="ue363bd5b" style="text-align: justify"><span data-lake-id="u2f45e311" id="u2f45e311" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">​</span><br></p>
  <blockquote data-lake-id="u824db0b3" id="u824db0b3">
   <p data-lake-id="u9aa30743" id="u9aa30743"><span data-lake-id="u5eef48a9" id="u5eef48a9">班长要组织全班同学聚餐，由于大家毕业多年，所以要逐个打电话敲定时间，时间初定10.1日。然后开始逐个打电话。</span></p>
   <p data-lake-id="uf7357901" id="uf7357901"><span data-lake-id="u0c8c2c1b" id="u0c8c2c1b">班长：小A，我们想定在10.1号聚会，你有时间嘛？有时间你就说YES，没有你就说NO，然后我还会再去问其他人，具体时间地点我会再通知你，这段时间你可先去干你自己的事儿，不用一直等着我。（</span><span data-lake-id="u73e2c970" id="u73e2c970">协调者询问事务是否可以执行，这一步不会锁定资源</span><span data-lake-id="u7ca67c5e" id="u7ca67c5e">）</span></p>
   <p data-lake-id="u751a11f3" id="u751a11f3"><span data-lake-id="u481c4738" id="u481c4738">小A：好的，我有时间。（</span><span data-lake-id="u8de65625" id="u8de65625">参与者反馈</span><span data-lake-id="ud20df214" id="ud20df214">）</span></p>
   <p data-lake-id="ubc0eb3a8" id="ubc0eb3a8"><span data-lake-id="u307b1910" id="u307b1910">班长：小B，我们想定在10.1号聚会……不用一直等我。</span></p>
   <p data-lake-id="u2b691955" id="u2b691955"><span data-lake-id="u3dcf73f7" id="u3dcf73f7">班长收集完大家的时间情况了，一看大家都有时间，那么就再次通知大家。（</span><span data-lake-id="ud5f875c3" id="ud5f875c3">协调者接收到所有YES指令</span><span data-lake-id="uca3d6101" id="uca3d6101">）</span></p>
   <p data-lake-id="u302de1ac" id="u302de1ac"><span data-lake-id="uf39d2ad7" id="uf39d2ad7">班长：小A，我们确定了10.1号聚餐，你要把这一天的时间空出来，这一天你不能再安排其他的事儿了。然后我会逐个通知其他同学，通知完之后我会再来和你确认一下，还有啊，如果我没有特意给你打电话，你就10.1号那天来聚餐就行了。对了，你确定能来是吧？（</span><span data-lake-id="u1e23ac71" id="u1e23ac71">协调者发送事务执行指令，这一步锁住资源。如果由于网络原因参与者在后面没有收到协调者的命令，他也会执行commit</span><span data-lake-id="u2f6e27a9" id="u2f6e27a9">）</span></p>
   <p data-lake-id="u84987362" id="u84987362"><span data-lake-id="ubdd2ecef" id="ubdd2ecef">小A顺手在自己的日历上把10.1号这一天圈上了，然后跟班长说，我可以去。（</span><span data-lake-id="ua1c44eb6" id="ua1c44eb6">参与者执行事务操作，反馈状态</span><span data-lake-id="u61e77fa4" id="u61e77fa4">）</span></p>
   <p data-lake-id="u731017f7" id="u731017f7"><span data-lake-id="u5654f2f4" id="u5654f2f4">班长：小B，我们决定了10.1号聚餐……你就10.1号那天来聚餐就行了。</span></p>
   <p data-lake-id="u5a2e9fde" id="u5a2e9fde"><span data-lake-id="uab270a29" id="uab270a29">班长通知完一圈之后。所有同学都跟他说：”我已经把10.1号这天空出来了”。于是，他在10.1号这一天又挨个打了一遍电话告诉他们：嘿，现在你们可以出门拉。。。。（</span><span data-lake-id="ud2dd4255" id="ud2dd4255">协调者收到所有参与者的ACK响应，通知所有参与者执行事务的commit</span><span data-lake-id="u58ebbe8d" id="u58ebbe8d">）</span></p>
   <p data-lake-id="u9e6ac559" id="u9e6ac559"><span data-lake-id="u54f34ab3" id="u54f34ab3">小A，小B：我已经出门拉。（执行commit操作，反馈状态）</span></p>
  </blockquote>
  <p data-lake-id="u4b9f4ef5" id="u4b9f4ef5"><span data-lake-id="ud2c9e76d" id="ud2c9e76d">​</span><br></p>
  <h3 data-lake-id="hYdQF" id="hYdQF"><span data-lake-id="uc3d9f1c8" id="uc3d9f1c8">3PC为什么比2PC好？</span></h3>
  <p data-lake-id="uabb094cb" id="uabb094cb"><span data-lake-id="u1ca49361" id="u1ca49361">直接分析前面我们提到的协调者和参与者都挂的情况。</span></p>
  <ul list="u30b8b497">
   <li fid="u41e43c2d" data-lake-id="uaf3b1909" id="uaf3b1909"><span data-lake-id="u5b966c0d" id="u5b966c0d">第二阶段协调者和参与者挂了，挂了的这个参与者在挂之前已经执行了操作。但是由于他挂了，没有人知道他执行了什么操作。</span></li>
  </ul>
  <ul list="u30b8b497" data-lake-indent="1">
   <li fid="u2f0d7b98" data-lake-id="u927fb235" id="u927fb235"><span data-lake-id="uace87291" id="uace87291">这种情况下，当新的协调者被选出来之后，他同样是询问所有的参与者的情况来决定是commit还是</span><span data-lake-id="u23fe06b0" id="u23fe06b0" class="lake-fontsize-12" style="color: rgb(85, 85, 85)">rollback</span><span data-lake-id="u01531ee8" id="u01531ee8">。这看上去和二阶段提交一样啊？他是怎么解决一致性问题的呢？</span></li>
   <li fid="u2f0d7b98" data-lake-id="uc1c02a69" id="uc1c02a69"><span data-lake-id="uc28919ad" id="uc28919ad">看上去和二阶段提交的那种数据不一致的情况的现象是一样的，但仔细分析所有参与者的状态的话就会发现其实并不一样。我们假设挂掉的那台参与者执行的操作是commit。那么其他没挂的操作者的状态应该是什么？他们的状态要么是prepare-commit要么是commit。因为3PC的第三阶段一旦有机器执行了commit，那必然第一阶段大家都是同意commit。所以，这时，新选举出来的协调者一旦发现未挂掉的参与者中有人处于commit状态或者是prepare-commit的话，那就执行commit操作。否则就执行rollback操作。这样挂掉的参与者恢复之后就能和其他机器保持数据一致性了。（为了简单的让大家理解，笔者这里简化了新选举出来的协调者执行操作的具体细节，真实情况比我描述的要复杂）</span></li>
  </ul>
  <p data-lake-id="u302b8c29" id="u302b8c29"><br></p>
  <p data-lake-id="ufbfb4b54" id="ufbfb4b54"><span data-lake-id="u8d7f68f8" id="u8d7f68f8">简单概括一下就是，如果挂掉的那台机器已经执行了commit，那么协调者可以从所有未挂掉的参与者的状态中分析出来，并执行commit。如果挂掉的那个参与者执行了rollback，那么协调者和其他的参与者执行的肯定也是rollback操作。</span></p>
  <p data-lake-id="u68d5b2d7" id="u68d5b2d7"><span data-lake-id="u7e356a77" id="u7e356a77">​</span><br></p>
  <p data-lake-id="u29cbf9ef" id="u29cbf9ef"><span data-lake-id="uc14b175d" id="uc14b175d">所以，再多引入一个阶段之后，3PC解决了2PC中存在的那种由于协调者和参与者同时挂掉有可能导致的数据一致性问题。</span></p>
  <h3 data-lake-id="xO5B3" id="xO5B3"><span data-lake-id="u0e021661" id="u0e021661">3PC存在的问题</span></h3>
  <p data-lake-id="u202f992d" id="u202f992d"><span data-lake-id="ud4bbc209" id="ud4bbc209">在doCommit阶段，如果参与者无法及时接收到来自协调者的doCommit或者rebort请求时，会在等待超时之后，会继续进行事务的提交。</span></p>
  <p data-lake-id="u570e42a4" id="u570e42a4"><span data-lake-id="u66e193e3" id="u66e193e3">​</span><br></p>
  <p data-lake-id="u01e0f8f7" id="u01e0f8f7"><span data-lake-id="uf6565daf" id="uf6565daf">所以，由于网络原因，协调者发送的abort响应没有及时被参与者接收到，那么参与者在等待超时之后执行了commit操作。这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。</span></p>
 </body>
</html>